package sfsoftware.util;

import java.util.Vector;
import sfsoftware.game.TrevorLegacy;
import sfsoftware.game.actor.attributes.*;
import sfsoftware.game.actor.item.Item;
import sfsoftware.game.actor.item.Ruple;
import sfsoftware.game.trevor.*;
import sfsoftware.game.trevor.ui.Dialog;
import sfsoftware.util.graphics.Particle;
import sfsoftware.util.graphics.Sprite;

public class Actor extends Sprite implements Collidable {		
	// Vector that stores all non-permanent actors per room
	public static Vector<Actor> cast = new Vector<Actor>();
	
	protected static int playingFieldX;
	protected static int playingFieldY;
	
	public static void setPlayingField(int x, int y)
	{
		playingFieldX = x;
		playingFieldY = y;
	}
	protected boolean bottomLeft = true;
	protected boolean bottomRight = true;	
	protected boolean topLeft = true;
	protected boolean topRight = true;
	
	// The last directions that were moved.
	protected double xDirection = 0;
	protected double yDirection = 0;
	
	protected int maxHealth = 1;		
	protected int health = maxHealth;
	
	protected int speed = 4;	
	private boolean isWalkable = true;
	
	private float accel = 0;
	protected float friction = 0.2f;
	
	public Actor(int destX, int destY, String texture, int destWidth, int destHeight) 
	{
		super(destX, destY, texture);
		height = destHeight;
		width = destWidth;	
		
		// Do not add permanent actors to our cast vector as this vector is cleared with each new room
		if(!(this instanceof Permanent))		
			cast.add(this);
	}
	
	public Actor(int destX, int destY, String texture, int srcX, int srcY, int destWidth, int destHeight) {
		super(destX, destY, texture);
		setSrcX(srcX);
		setSrcY(srcY);
		height = destHeight;
		width = destWidth;	
		
		// Do not add the Player to our cast vector as this vector is cleared with each new room
		if(!(this instanceof Permanent))			
			cast.add(this);
	}
	
	public void accelerate()
	{		
		move(xDirection * getAcceleration(), yDirection * getAcceleration(), TrevorLegacy.tileMap);				
				
		if(Math.round(getAcceleration()) > 0) 
		{
			setAcceleration(getAcceleration() - friction);
		}
		else if(Math.round(getAcceleration()) < 0) 
		{
			setAcceleration(getAcceleration() + friction);
		}	
		else if(Math.round(getAcceleration()) == 0)
		{
			setAcceleration(0);
		}
		
	}
	
	protected boolean collidingWithCast(double dirX, double dirY)
	{				
		boolean b = false;
		// Copy the Actor vector to an array to avoid concurrent modification issues
		Actor[] c = new Actor[cast.size()];
		cast.toArray(c);	
		
		for(int i = 0; i < c.length; i++)
		{
			// The cast vector can only hold Actor objects so no need to use instanceof
			Actor a  = (Actor) c[i];			
			// Only check for collision if the current Actor isn't walkable and isn't us!
			if(!(a.equals(this)) && !(a.isWalkable))
			{
				// Destination X and Y IF the Actor moves
				int newX = (int) (getX() + Math.round(getSpeed() * dirX));
				int newY = (int) (getY() + Math.round(getSpeed() * dirY));				
				// Create a temporary Actor object representing where OUR actor WILL move
				Actor temp = new Actor(newX, newY, "", 0, 0, getWidth(), getHeight());								
				// Check using our collidingWith method if the two objects are colliding!
				if(a.collidingWith(temp))
				{
					// Return true - this will prevent movement from happening.
					b = true;
					// Call the collisionResult method to trigger any expected behavior
					a.collisionResult(this);
				}
				
			}
		}
		
		return b;
	}
	
	@Override
	public boolean collidingWith(Collidable object) 
	{		
		boolean isColliding = false;
		if(object instanceof Actor) // && objB instanceof Actor)
		{
			Actor a = (Actor) object;
			Actor b = this; //(Actor) objB;
			
			// Calculate distance from center points of sprites - hence the extra math
			int xDistance = (int) ((b.getX() + (b.getWidth() / 2)) - (a.getX() + (a.getWidth() / 2)));
			int yDistance = (int) ((b.getY() + (b.getHeight() / 2)) - (a.getY() + (a.getHeight() / 2)));

			if (Math.sqrt((xDistance * xDistance) + (yDistance * yDistance)) < (b.getWidth() / 2) + (a.getWidth() / 2)) {
				isColliding = true;
			}
			
		}
		return isColliding;
	}
	
	@Override
	public void collisionResult(Collidable object) {		
		if(object.equals(TrevorLegacy.player))
		{
			if(this instanceof Wiffable && Player.isUsingWiffa())
			{
				Wiffable w = (Wiffable) this;
				w.wiffaHit();
			}
		}
	}
	
	protected int distanceToActor(Actor a)
	{
		int x1 = (int) (a.getX() + (a.getWidth() / 2));
		int x2 = (int) (getX() + (getWidth() / 2));
		int y1 = (int) (a.getY() + (a.getHeight() / 2));
		int y2 = (int) (getY() + (getHeight() / 2));
		
		return (int) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
	}
	
	protected int distanceToPoint(int x, int y)
	{
		int myX = (int) (getX() + (getWidth() / 2));
		int myY = (int) (getY() + (getHeight() / 2));
		
		return (int) Math.sqrt((x - myX) * (x - myX) + (y - myY) * (y - myY));		
	}
	
	public void draw()
	{
		super.draw(width, height);
	}
	
	protected void generateParticles(int particleCount, float red, float green, float blue)
	{
		for(int i = 0; i < particleCount; i++)
		{
			new Particle((int)(getX() + (int) (Math.random() * getWidth())), (int)(getY() + (int) (Math.random() * getHeight())), red, green, blue, 2);
		}
	}
	
	protected void generateParticles(int particleCount, float red, float green, float blue, int size)
	{
		for(int i = 0; i < particleCount; i++)
		{
			new Particle((int)(getX() + (int) (Math.random() * getWidth())), (int)(getY() + (int) (Math.random() * getHeight())), red, green, blue, size);
		}
	}

	public float getAcceleration()
	{
		return accel;
	}
	
	protected double getAngleFromActor(Actor a)
	{
		return getAngle(a, this);
	}

	protected double getAngleToActor(Actor b)
	{
		return getAngle(this, b);
	}
		
	public static double getAngle(Actor a, Actor b)
	{
		double dx = (a.getX() + (a.getWidth() / 2)) - (b.getX() + (b.getWidth() / 2));
		double dy = (a.getY() + (a.getHeight() / 2)) - (b.getY() + (b.getHeight() / 2));
		
		double inRads = Math.atan2(dy,dx);
		
		if (inRads < 0)
		{
	        inRads = Math.abs(inRads);
		}
	    else
	    {
	        inRads = 2 * Math.PI - inRads;
	    }			
		
		return inRads;
	}	
	
	protected Tile getCurrentTile()
	{
		int xCenter = (int) getX() + (width / 2);
		int xTile = (int) Math.floor((xCenter - playingFieldX) / 40);

		int yCenter = (int) getY() + (height / 2);		
		int yTile = (int) Math.floor((yCenter - playingFieldY) / 40);	
		
		return TrevorLegacy.tileMap[xTile][yTile];
	}
	
	protected int getSpeed() 
	{
		return speed;
	}		
	
	protected boolean getWalkable()
	{
		return isWalkable;
	}
	
	/**
	 * Moves an Actor without checking any collision
	 * 
	 * @param dirX
	 * @param dirY
	 */
	protected void moveWithoutCheckingCollision(double dirX, double dirY)
	{
		super.move((float) (this.getX() + (dirX * speed)), (float) (this.getY() + (dirY * speed)));
	}
	
	/**
	 * Tries to move an Actor - checks for collision with a supplied tile map
	 * 
	 * @param 	dirX
	 * @param 	dirY
	 * @param 	tileMap
	 */
	public boolean move(double dirX, double dirY, Tile[][] tileMap)
	{			
		boolean successfulMove = false;
		if(!(sfsoftware.game.TrevorLegacy.isPaused) && !(Dialog.displayDialog))
		{
			
			int speed = this.speed;
			if(getAcceleration() != 0)
			{
				speed = Math.round(getAcceleration());
			}
			
			// Store the directions the Actor tries to move.
			//xDirection = (int) dirX;
			//yDirection = (int) dirY;
			
			// TODO Support for sprites bigger than the tile size!
			int x = (int) (this.getX() + (width / 2));
			int y = (int) (this.getY() + (height / 2));		
			
			int xTile = (int) Math.floor(((x - playingFieldX) / 40));
			int yTile = (int) Math.floor(((y - playingFieldY) / 40));													
			
			if(!(collidingWithCast(dirX, dirY)))
			{				
				updateCornerVariables(x, (int) (y + Math.round((speed * dirY))), tileMap);
				if(dirY < 0)
				{
					if(topLeft && topRight)
					{
						super.move(this.getX(), (float) (this.getY() + (dirY * speed)));
						successfulMove = true;
					}
					else if(height <= 40)
					{				
						super.move(this.getX(), playingFieldY + (yTile * 40));
					}
				}
				if(dirY > 0)
				{
					if(bottomLeft && bottomRight)
					{
						super.move(this.getX(), (float) (this.getY() + (dirY * speed)));
						successfulMove = true;
					}
					else if(height <= 40)
					{
						super.move(this.getX(), playingFieldY + (yTile * 40) + (40 - height));
					}			
				}
				updateCornerVariables((int) (x + Math.round((speed * dirX))), y, tileMap);		 
				if(dirX < 0)
				{
					if(topLeft && bottomLeft)
					{
						super.move((float) (this.getX() + (dirX * speed)), this.getY());
						successfulMove = true;
					}
					else if(width <= 40)
					{
						super.move(playingFieldX + (xTile * 40), this.getY());								
					}
				}
				if(dirX > 0)
				{
					if(topRight && bottomRight)
					{
						super.move((float) (this.getX() + (dirX * speed)), this.getY());
						successfulMove = true;
					}
					else if(width <= 40)
					{
						super.move(playingFieldX + (xTile * 40) + (40 - width), this.getY());
					}			
				}
			}
		}
		return successfulMove;
	}

	/**
	 * Calls the freeze method for all Actors who are Freezable
	 */
	public static void freezeCast()
	{
		Object[] c = new Object[cast.size()];
		cast.toArray(c);	
		
		for(int i = 0; i < c.length; i++)
		{
			if(c[i] instanceof Freezable)
			{
				((Freezable) c[i]).freeze();
			}
		}
	}
	
	/**
	 * Toggles poisoning for all Actors who are Poisonable
	 */
	public static void poisonCast()
	{
		Object[] c = new Object[cast.size()];
		cast.toArray(c);	
		
		for(int i = 0; i < c.length; i++)
		{
			if(c[i] instanceof Poisonable)
			{
				((Poisonable) c[i]).togglePoisoned();
			}
		}
	}
	
	public static void remove(Actor a)
	{
		cast.remove(a);
	}
	
	protected void remove()
	{
		cast.remove(this);
	}
	
	protected void setAcceleration(float a)
	{		
		accel = a;
	}
	
	protected void setFriction(float f)
	{
		friction = f;
	}
	
	protected void setSpeed(int newSpeed)
	{
		speed = newSpeed;
	}
	
	protected void setWalkable(boolean b)
	{
		isWalkable = b;
	}

	@Override
	public String toString()
	{
		return "Actor \"" + tex + "\" @ " + (int) getX() + ", " + (int) getY();		
	}

	/**
	 * Snaps an Actor to the tile on the playing field that there center coordinates sit in.
	 */
	public void snap()
	{
		snapX();
		snapY();
	}
	
	protected void snapX()
	{
		setX((int) getCurrentTile().getX());
	}
	
	protected void snapY()
	{				
		setY((int) getCurrentTile().getY());		
	}
		
	protected void updateCornerVariables(int x, int y, Tile[][] tileMap) 
	{
		int upY = (int) Math.floor(((y - playingFieldY) - (height / 2)) / 40);
		int downY = (int) Math.floor((((y - playingFieldY) + (height / 2)) - 1) / 40);
		int leftX = (int) Math.floor(((x - playingFieldX) - (width / 2)) / 40);
		int rightX = (int) Math.floor((((x - playingFieldX)  + (width / 2)) - 1) / 40);
				
		try {
			topLeft = tileMap[leftX][upY].isWalkable();	
			//System.out.println("Top Left is: " + leftX + ", " + upY);
		}
		catch(ArrayIndexOutOfBoundsException e)
		{
			//System.out.println("Player top left is out of bounds");
			topLeft = true;
		}
		try {
			bottomLeft = tileMap[leftX][downY].isWalkable();
			//System.out.println("Bottom Left is: " + leftX + ", " + downY);
		}
		catch(ArrayIndexOutOfBoundsException e)
		{
			//System.out.println("Player bottom left is out of bounds");
			bottomLeft = true;
		}
		try {
			topRight = tileMap[rightX][upY].isWalkable();
			//System.out.println("Top Right is: " + rightX + ", " + upY);
		}
		catch(ArrayIndexOutOfBoundsException e)
		{
			//System.out.println("Player top right is out of bounds");
			topRight = true;
		}
		try {		
			bottomRight = tileMap[rightX][downY].isWalkable();	
			//System.out.println("Bottom Right is: " + rightX + ", " + downY);
		}
		catch(ArrayIndexOutOfBoundsException e)
		{
			//System.out.println("Player bottom right is out of bounds");
			bottomRight = true;
		}									
	}

	/**
	 * Updates all Actors - this includes drawing
	 */
	public static void updateCast() {		
		// Copy the Actor vector to an array to avoid concurrent modification issues
		Actor[] c = new Actor[cast.size()];
		cast.toArray(c);	
		
		for(int i = 0; i < c.length; i++)		
		{
			Actor a = (Actor) c[i];
			if(a instanceof Updatable)
			{
				((Updatable) a).update();
			}			
			if(a.collidingWith(TrevorLegacy.player))
			{
				// If the player collides with an actor, do something!
				a.collisionResult(TrevorLegacy.player);
				
				// If an item is able to be picked up - remove it!
				if(a instanceof Obtainable && !(a instanceof Ruple) && !(a instanceof Item))
				{
					// Colliding with "Obtainable" objects removes them from the screen
					cast.remove(a); // i
				}
			}
			
			// Accelerating!
			if(a.getAcceleration() != 0)
			{
				a.accelerate();
			}
			
			a.draw();
		}				
	}
	
}
